using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading;

namespace UnitTesting.Fixturing
{
	class StaticCompositeValueProvider : ICompositeValueProvider,
	                                     IValueProviderCustomizable,
	                                     IValueProviderReplaceable,
	                                     ICloneable<ICompositeValueProvider>
	{
		readonly List<IValueProvider> _valueProviders;

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		public int Count
		{
			get { return _valueProviders.Count; }
		}

		public IEnumerable<IValueProvider> ValueProviders
		{
			get { return _valueProviders; }
		}

		internal StaticCompositeValueProvider(params IValueProvider[] valueProviders) : this(new List<IValueProvider>(valueProviders))
		{
		}

		protected StaticCompositeValueProvider(List<IValueProvider> valueProviders)
		{
			_valueProviders = valueProviders;
		}

		public object Create(Type type)
		{
			Ensure.IsNotNull(() => type);
			Increment();
			try
			{
				foreach (var value in GetResolvedValues(type))
				{
					EnsureIsType(value, type);
					return value;
				}
				return NotResolved.Value;
			}
			finally
			{
				Decrement();
			}
		}

		IEnumerable<object> GetResolvedValues(Type type)
		{
			return _valueProviders.Select(valueProvider =>
			{
				var value = valueProvider.Create(type);

				WirteValueProviderTrace(valueProvider,
				                        targetType: type,
				                        targetValue: value);

				return value;
			}).Where(IsResolvedValue);
		}

		static bool IsResolvedValue(object result)
		{
			return result != NotResolved.Value;
		}

		static void EnsureIsType(object value, Type type)
		{
			if (value == null)
			{
				EnsureIsNotValueType(type);
			}
			else
			{
				EnsureIsInstanceOfType(value, type);
			}
		}

		static void EnsureIsNotValueType(Type type)
		{
			if (type.IsValueType)
				throw new ValueTypeMismatchedException(type);
		}

		static void EnsureIsInstanceOfType(object value, Type type)
		{
			if (!type.IsInstanceOfType(value))
				throw new ValueTypeMismatchedException(type, value);
		}

		public virtual void ReplaceWithType(IValueProvider valueProvider)
		{
			Ensure.IsNotNull(() => valueProvider);
			int index = FindIndex(valueProvider.GetType());
			Replace(index, valueProvider);
		}

		int FindIndex(Type type)
		{
			int index = _valueProviders.FindIndex(0, type.IsInstanceOfType);
			EnsureIsExists(type, index);
			return index;
		}

		static void EnsureIsExists(Type valueProviderType, int index)
		{
			if (index < 0)
			{
				throw new InvalidOperationException(
					string.Format(CultureInfo.InvariantCulture,
					              "No value provider for the type '{0}'", valueProviderType));
			}
		}

		public virtual void Replace(int index, IValueProvider valueProvider)
		{
			_valueProviders.RemoveAt(index);
			_valueProviders.Insert(index, valueProvider);
		}

		public void InsertBeforeType(IValueProvider valueProvider)
		{
			Ensure.IsNotNull(() => valueProvider);
			int index = FindIndex(valueProvider.GetType());
			_valueProviders.Insert(index, valueProvider);
		}

		public virtual T FindFirstAs<T>()
		{
			int index = FindIndex(typeof (T));
			return (T) _valueProviders[index];
		}

		public void Add(params IValueProvider[] valueProviders)
		{
			Ensure.IsNotAnyNull(() => valueProviders);
			valueProviders.ForEach(x => _valueProviders.Add(x));
		}

		public void InsertAtFirst(IValueProvider valueProvider)
		{
			Insert(0, valueProvider);
		}

		public void Insert(int index, IValueProvider valueProvider)
		{
			_valueProviders.Insert(index, valueProvider);
		}

		public void RemoveLoosely(IValueProvider valueProvider)
		{
			_valueProviders.Remove(valueProvider);
		}

		public void RemoveLooselyAt(int index)
		{
			if (IsValidIndex(index))
				RemoveAt(index);
		}

		void RemoveAt(int index)
		{
			_valueProviders.RemoveAt(index);
		}

		bool IsValidIndex(int index)
		{
			return index >= 0 && index < _valueProviders.Count;
		}

		public void Clear()
		{
			_valueProviders.ToArray()
				.ForEach(x => _valueProviders.Remove(x));
		}

		public virtual ICompositeValueProvider Clone()
		{
			return new StaticCompositeValueProvider(ValueProviders.ToArray());
		}

		#region VALUE_PROVIDER_TRACE

		static int _intantCount = -1;

		[Conditional("VALUE_PROVIDER_TRACE")]
		static void Increment()
		{
			Interlocked.Increment(ref _intantCount);
		}

		[Conditional("VALUE_PROVIDER_TRACE")]
		static void Decrement()
		{
			Interlocked.Decrement(ref _intantCount);
		}

		[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Console.WriteLine(System.String,System.Object,System.Object,System.Object)"), Conditional("VALUE_PROVIDER_TRACE")]
		static void WirteValueProviderTrace(IValueProvider valueProvider, Type targetType, object targetValue)
		{
			WriteIntent();

			string valueProviderFormat = valueProvider.ToString();
			string targetTypeFormat = targetType.Name;
			string targetValueFormat = targetValue == NotResolved.Value ? "\"\"" : "\"" + targetValue + "\"";
			Console.WriteLine("{0} : {1} = {2}", valueProviderFormat, targetTypeFormat, targetValueFormat);
		}

		[Conditional("VALUE_PROVIDER_TRACE")]
		static void WriteIntent()
		{
			Console.Write(Enumerable.Range(0, _intantCount).Aggregate("", (s, i) => s + "\t"));
		}

		#endregion
	}
}